
package server;

import java.net.Socket;
import java.util.Vector;

import nanoxml.XMLElement;
import utils.AbstractConnectionThread;
import utils.IConnection;
import utils.ITransmittable;
import utils.ServerInfo;
import utils.Table;
import utils.TableList;
import utils.User;
import utils.UserList;

import commands.CommandBringStatistics;
import commands.CommandChatMessage;
import commands.CommandJoinTable;
import commands.CommandLeaveTable;
import commands.CommandNewTable;
import commands.CommandNewUser;
import commands.CommandNotifyDBCrashed;
import commands.CommandOtherPlayerName;
import commands.CommandPlayerDisconnected;
import commands.CommandUserAlreadyConnected;
import commands.CommandUserExit;
import commands.CommandUserLogon;
import commands.Commands;

import enums.PlayerColor;

/**
 * This is the server's connection with the client
 */
public class ConnectionThreadRPSServerToClient extends AbstractConnectionThread implements IConnection{
	
	private ServerInfo serverInfo;
	private RPSServer server;
	private UserList userList = null;
	private TableList tableList = null;	
	
	public ConnectionThreadRPSServerToClient (Socket clientSocket) {
		super (clientSocket);
		this.server = RPSServer.getInstance();
	}

	public void init (String username) {
		this.username = username;
		this.serverInfo = server.getGameInfo();
		this.userList = serverInfo.getUserList();
		this.tableList = serverInfo.getTableList();
	}
	
	/**
	 * Parse the command and perform the required action
	 */
	public void parse (XMLElement command) {
		try {
			if (command.getName().equals(Commands.USER_LOGON)) {
				if (server.getDbOn()) {
					CommandUserLogon commandUserLogon = new CommandUserLogon(command);
					String username = commandUserLogon.getUsername();
					this.init (username);
					if (userList.containsUser(username) == true) {
						CommandUserAlreadyConnected commandUserAlreadyConnected = new CommandUserAlreadyConnected(username, "Unable to connect. User with this name already connected.");
						send(commandUserAlreadyConnected);
						stopLoop();
						socket.close();
					}
					else {
						server.getConnections().addConnection(username, this);
						server.getDBconn().send(command);
					}
				}
				else {		
					send(new CommandNotifyDBCrashed("Unable to connect. Please try again later", "logonPanel"));
					stopLoop();
					socket.close();
				}
			}
			else if (command.getName().equals(Commands.NEW_USER)) {
				if (server.getDbOn()) {
					CommandNewUser commandNewUser = new CommandNewUser(command);
					String username = commandNewUser.getUsername();
					this.init (username);
					if (userList.containsUser(username) == true) {
						CommandUserAlreadyConnected commandUserAlreadyConnected = new CommandUserAlreadyConnected(username, "Unable to register. User with this name already exists.");
						send(commandUserAlreadyConnected);
						stopLoop();
						socket.close();
					}
					else {
						server.getConnections().addConnection(username, this);
						server.getDBconn().send(command);
					}
				}
				else {
					send(new CommandNotifyDBCrashed("Unable to connect. Please try again later", "logonPanel"));
					stopLoop();
					socket.close();
				}
			}
			else if (command.getName().equals(Commands.BRING_STATISTICS)) {
				if (server.getDbOn()) {
					CommandBringStatistics commandBringStatistics = new CommandBringStatistics(command);
					String username = commandBringStatistics.getUsername();
					this.init (username);
					server.getConnections().addConnection(username, this);
					server.getDBconn().send(command); 
				}
				else {
					send(new CommandNotifyDBCrashed("Unable to connect. Please try again later","clientPanel"));
				}
			}
			else if (command.getName().equals(Commands.USER_VERIFIED)) {
				addNewUser(command.getStringAttribute("Username"));
			}
			else if (command.getName().equals(Commands.NEW_TABLE)) {
				CommandNewTable commandNewTable = new CommandNewTable(command);
				addNewTable(commandNewTable);
			}
			else if (command.getName().equals(Commands.JOIN_TABLE)) {
				joinTable(new CommandJoinTable(command));
			}
			else if (command.getName().equals(Commands.LEAVE_TABLE)) {
				CommandLeaveTable commandLeaveTable = new CommandLeaveTable(command);
				leaveTable(commandLeaveTable);
			}
			else if (command.getName().equals(Commands.CHAT_MESSAGE)) {
				CommandChatMessage commandChatMessage = new CommandChatMessage(command);	
				commandChatMessage.setUsernameFrom(username);
				broadcast(username, commandChatMessage);
			}
			else if (command.getName().equals(Commands.USER_EXIT)) {
				removeUser();
			}
			else if (command.getName().equals(Commands.OTHER_PLAYER_NAME)) {
				CommandOtherPlayerName commandOtherPlayerName = new CommandOtherPlayerName(command);
				setOtherPlayerName(commandOtherPlayerName);
			}
			else if (command.getName().equals(Commands.PLAYER_DISCONNECTED)) {
				// Notify to all the viewers that the player disconnected
				CommandPlayerDisconnected commandPlayerDisconnected = new CommandPlayerDisconnected(command);
				broadcast(commandPlayerDisconnected);
				
				String usernameDisconnected = commandPlayerDisconnected.getUsername();
				int tableNum = commandPlayerDisconnected.getTableNum();
				
				serverInfo.getTableList().removePlayer(tableNum, usernameDisconnected);
				
				Table tableOfOwner = serverInfo.getTableList().getTable(tableNum);
				if (tableOfOwner != null && usernameDisconnected.equals(tableOfOwner.getOwnerUsername()))
				{
					serverInfo.getTableList().removeTable(tableNum);
				}
				userList.removeUser(usernameDisconnected);
			}
			else if (command.getName().equals(Commands.GAME_RESULT)) {
				if (server.getDbOn()) {
					server.getDBconn().send(command);
				}
			}
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private void addNewTable(CommandNewTable commandNewTable) {
		String username = commandNewTable.getUsername();
		String ownerIP = commandNewTable.getOwnerIP();
		
		tableList.addTable(userList.getUser(username), ownerIP , PlayerColor.RED, commandNewTable.getGameSettings());
		this.broadcast(commandNewTable);
	}

	private void joinTable(CommandJoinTable commandJoinTable) {
		String username = commandJoinTable.getUsername();
		int tableNum = commandJoinTable.getTableNum();
		this.serverInfo.getTableList().getTable(tableNum).addPlayer(new User(username));
		
		broadcast(username, commandJoinTable);
	}

	public void leaveTable(CommandLeaveTable commandLeaveTable)
	{
		String usernameLeaveTable = commandLeaveTable.getUsername();
		int tableNum = commandLeaveTable.getTableNum();
		Table table = serverInfo.getTableList().getTable(tableNum);
		
		serverInfo.getTableList().removePlayer(tableNum, usernameLeaveTable);
		
		if (usernameLeaveTable.equals(table.getOwnerUsername()))
		{
			serverInfo.getTableList().removeTable(tableNum);
		}
		else if (usernameLeaveTable.equals(table.getOtherPlayerUsername()))
		{
			table.setOtherPlayerUsername("");
		}
		
		broadcast(commandLeaveTable);
	}

	private void setOtherPlayerName(CommandOtherPlayerName commandOtherPlayerName)
	{
		String otherPlayerName = commandOtherPlayerName.getOtherPlayerName();
		int tableNum = commandOtherPlayerName.getTableNum();
		
		serverInfo.getTableList().getTable(tableNum).setOtherPlayerUsername(otherPlayerName);
		
		broadcast(commandOtherPlayerName);
	}

	public void addNewUser(String username) {
		User user = new User(username);
		userList.addUser(user);
		
		this.transmit (this.getGameInfo());
		this.broadcast(username,user);
	}
	
	private void removeUser()
	{
		// If the user who left is a table's owner, remove this table from the list
		Table tableOwnedByMe = serverInfo.getTableList().getTableByOwner(username);
		
		if (tableOwnedByMe != null)
		{
			tableList.removeTable(tableOwnedByMe.getTableNum());
		}
		
		// Remove the user from the server's users list
		userList.removeUser(username);
			
		// Notify the rest of the users that the user has exited
		CommandUserExit commandUserExit = new CommandUserExit(username);
		broadcast(commandUserExit);
	}
	
	public void broadcast (ITransmittable element) {
		broadcast("", element);
	}
	
	public void broadcast (String omitUser, ITransmittable element) {
		// retrieve all the clients
		Vector<String> users = userList.getUsersNames();
		for (int i = 0; i < users.size(); i++) {
			String currentUsername = (String)users.get(i);
			if (!omitUser.equals(currentUsername))
				transmit(currentUsername, element);
		}
	}
	
	private void transmit(String username,ITransmittable element) {
		ConnectionThreadRPSServerToClient conn = (ConnectionThreadRPSServerToClient) server.getConnections().getConnection (username);

		// Send the object to the user
		conn.send (element);
	}
	
	private void transmit(ITransmittable element) {
		send(element);
	}


	private ServerInfo getGameInfo() {
		return this.serverInfo;
	}

	public UserList getUserList () {
		return this.userList;
	}
	
	public TableList getTableList () {
	    return this.tableList;
	}
	
	@Override
	public void handleDisconnection()
	{
		removeUser();
	}

	@Override
	public String getUsername() {
		return null;
	}

	@Override
	public Table getTable() {
		return null;
	}
}